<?php

declare(strict_types=1);

namespace App\Service;

/**
 * es文章存取
 * contact  593809624@qq.com
 * author   刘传望
 * date 2025-10-16
 */
use App\Repository\ArticleFavoriteRepository;
use App\Repository\ArticleRepository;
use App\Repository\FavoriteFolderRepository;
use \Elasticsearch\Client;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use Monolog\Logger;

class SearchService
{
    #[Inject]
    private Client $elasticsearch;

    #[Inject]
    private Redis $redis;

    #[Inject]
    private ArticleFavoriteRepository $articleFavoriteRepository;

    #[Inject]
    private FavoriteFolderRepository $favoriteFolderRepository;

    #[Inject]
    private ArticleRepository $articleRepository;


    private const ARTICLE_INDEX = 'articles';

    private const REDIS_LIKE_SET_PREFIX = 'article:like:set:'; // 文章点赞用户集合
    private const REDIS_LIKE_COUNT_PREFIX = 'article:like:count:'; // 文章点赞数缓存

    /**
     * 同步文章到搜索引擎
     */
    public function syncArticleToES($article): void
    {
        // 未发布则从 ES 删除
        if ($article['status']  !== 'published') {
            $this->deleteArticleFromES((int) ($article['id'] ?? 0));
            return;
        }
    
        $publishedAt = str_replace(' ', 'T', $article['published_at']) . 'Z';
     

        // 组装写入 ES 的文档
        $doc = [
            'id' => (int) ($article['id'] ?? 0),
            'title' => (string) ($article['title'] ?? ''),
            'summary' => $article['summary'] ?? ($article['content'] ?? ''),
            'content' => (string) ($article['content'] ?? ''),
            'cover_image' => (string) ($article['cover_image'] ?? ''),
            'status' => (string) ($article['status'] ?? 'draft'),
            'view_count' => (int) ($article['view_count'] ?? 0),
            'like_count' => (int) ($article['like_count'] ?? 0),
            'comment_count' => (int) ($article['comment_count'] ?? 0),
            'reading_time' => (int) ($article['reading_time'] ?? 0),
            'user_id' => (int) ($article['user_id'] ?? 0),
            'user' => $article['user'] ?? null,
            'tags' => $article['tags'], // 对象数组，符合当前索引的 object 映射
            'published_at' => $publishedAt, // date 字段
        ];

        $params = [
            'index' => self::ARTICLE_INDEX,
            'id' => $doc['id'],
            'body' => $doc,
            'refresh' => 'true',
        ];

        try {
            $this->elasticsearch->index($params);
        } catch (\Throwable $e) {
            Logger::error('同步文章到ES失败: ' . $e->getMessage(), [
                'article_id' => $doc['id'] ?? 0
            ]);
        }
    }

    /**
     * 搜索文章
     * @param string $query 关键词
     * @param array $filters 筛选项
     */
    public function searchArticles(string $query, array $filters = [],$user_id=0, int $page = 1, int $size = 20): array
    {
        $from = ($page - 1) * $size;

        $filtersBuilt = $this->buildFilters($filters);
        $hasQuery = isset($query) && trim($query) !== '';

        $esQuery = $hasQuery
            ? [
                'bool' => [
                    'must' => [
                        'multi_match' => [
                            'query' => $query,
                            'fields' => ['title^3', 'content^2', 'tags^2', 'user.name'],
                            'type' => 'best_fields'
                        ]
                    ],
                    'filter' => $filtersBuilt
                ]
            ]
            : [
                'bool' => [
                    // 无关键词时仅按 filter + sort 返回所有
                    'filter' => $filtersBuilt
                ]
            ];

        $searchParams = [
            'index' => self::ARTICLE_INDEX,
            'from' => $from,
            'size' => $size,
            'body' => [
                'query' => $esQuery,
                'highlight' => [
                    'fields' => [
                        'title' => [
                            'number_of_fragments' => 0
                        ],
                        'content' => [
                            'fragment_size' => 150,
                            'number_of_fragments' => 3
                        ]
                    ]
                ],
                'sort' => $this->buildSort($filters['sort'] ?? 'relevance')
            ]
        ];

        $response = $this->elasticsearch->search($searchParams);

        return $this->formatSearchResults($response,$user_id);
    }

    public function deleteArticleFromES(int $articleId): void
    {
        $params = [
            'index' => self::ARTICLE_INDEX,
            'id' => $articleId,
        ];

        try {
            $this->elasticsearch->delete($params);
        } catch (\Elasticsearch\Common\Exceptions\Missing404Exception $e) {
            // 文档不存在，忽略
        } catch (\Throwable $e) {
            // 其他异常记录错误日志
            Logger::error('从ES删除文章失败: ' . $e->getMessage(), [
                'article_id' => $articleId
            ]);
        }
    }

    private function buildFilters(array $filters): array
    {
        $esFilters = [];

        if (!empty($filters['user_id'])) {
            $esFilters[] = ['term' => ['user_id' => $filters['user_id']]];
        }

        if (!empty($filters['tags'])) {
            $esFilters[] = ['terms' => ['tags.name.keyword' => $filters['tags']]];
        }

        if (!empty($filters['category'])) {
            $esFilters[] = ['term' => ['category' => $filters['category']]];
        }

        if (!empty($filters['date_range'])) {
            $esFilters[] = [
                'range' => [
                    'published_at' => [
                        'gte' => $filters['date_range']['start'],
                        'lte' => $filters['date_range']['end']
                    ]
                ]
            ];
        }

        // 只搜索已发布文章
        $esFilters[] = ['term' => ['status' => 'published']];

        return $esFilters;
    }

    private function buildSort(?string $sort): array
    {
        return match($sort) {
            'latest' => [['published_at' => 'desc']],
            'popular' => [
                ['view_count' => 'desc'],
                ['like_count' => 'desc'],
                ['published_at' => 'desc']
            ],
            default => ['_score'] // 相关性排序
        };
    }

    private function formatSearchResults(array $response,$user_id): array
    {
        $hits = $response['hits']['hits'] ?? [];
        $total = $response['hits']['total']['value'] ?? 0;

        $results = [];
        foreach ($hits as $hit) {
            $source = $hit['_source'];
            $highlight = $hit['highlight'] ?? [];

            $data = [
                'id' => $source['id'],
                'title' => $highlight['title'][0] ?? $source['title'],
                'summary' => $highlight['content'][0] ?? $source['summary'],
                'user' => $source['user'],
                'cover_image' => $source['cover_image'],
                'tags' => $source['tags'],
                'view_count' => $source['view_count'],
                'like_count' => $source['like_count'],
                'comment_count' => $source['comment_count'],
                'reading_time' => $source['reading_time'],
                'published_at' => $source['published_at'],
                'user_id' => $source['user_id'],
                'highlight' => $highlight
            ];

            $results[] = $this->fillArticle($data,$user_id);
        }

        return [
            'items' => $results,
            'total' => $total
        ];
    }

    /**
     * 根据关键词获取搜索建议（标题优先，内容次之）。
     * 说明：未使用 completion suggester，基于 match_phrase_prefix 兼容当前映射。
     */
    public function getSuggestions(string $keyword = '', int $size = 10): array
    {
        $size = max(1, min(50, $size));

        $hasKeyword = trim($keyword) !== '';

        $query = $hasKeyword
            ? [
                'bool' => [
                    'should' => [
                        ['match_phrase_prefix' => ['title' => ['query' => $keyword]]],
                        ['match_phrase_prefix' => ['content' => ['query' => $keyword]]]
                    ],
                    'minimum_should_match' => 1,
                    'filter' => [ ['term' => ['status' => 'published']] ]
                ]
            ]
            : [
                'bool' => [
                    'filter' => [ ['term' => ['status' => 'published']] ]
                ]
            ];

        $params = [
            'index' => self::ARTICLE_INDEX,
            'size' => $size,
            '_source' => ['title'],
            'body' => [
                'query' => $query,
                'sort' => $hasKeyword
                    ? [['_score' => 'desc'], ['view_count' => 'desc'], ['published_at' => 'desc']]
                    : [['view_count' => 'desc'], ['published_at' => 'desc']]
            ],
        ];

        $resp = $this->elasticsearch->search($params);
        $hits = $resp['hits']['hits'] ?? [];

        $titles = [];
        foreach ($hits as $hit) {
            $t = $hit['_source']['title'] ?? '';
            if ($t !== '' && !in_array($t, $titles, true)) {
                $titles[] = $t;
            }
            if (count($titles) >= $size) {
                break;
            }
        }

        return $titles;
    }

    /**
     * 从 ES 获取文章详情（按 ID）
     */
    public function getArticleFromESById(int $id,$user_id): array
    {
        $params = [
            'index' => self::ARTICLE_INDEX,
            'id' => $id,
        ];

        try {
            $resp = $this->elasticsearch->get($params);
            if (!isset($resp['_source'])) {
                $row = $this->articleRepository->getRowById($id,true);
                return $row;
            }
            $src = $this->fillArticle($resp['_source'],$user_id);
            // 统一返回结构，便于前端复用
            return $src;
        } catch (\Elasticsearch\Common\Exceptions\Missing404Exception $e) {
            return [];
        }
    }

    /**
     * 获取文章是否已经点赞，使用 Redis即可
     * @param int $articleId
     * @param int $userId
     * @return array{liked:bool,like_count:int}
     */
    public function isLike(int $articleId, int $userId): bool
    {
        $setKey = self::REDIS_LIKE_SET_PREFIX . $articleId;

        return $this->redis->sismember($setKey,$userId);
    }

    protected function fillArticle(array $data,$user_id): array
    {
        $data['is_like'] = $data['is_favorite'] = false;

        if ($user_id) {
            $data['is_like'] = $this->isLike((int)$data['id'], (int)$user_id);

            // 检查是否已收藏
            $existingFavorite = $this->articleFavoriteRepository->findExistingFavorite($user_id, $data['id'], 0);

            $data['is_favorite'] = $existingFavorite ? true : false;

            $data['favorite_count'] = count($this->favoriteFolderRepository->getUserFolders($user_id));
        }

        $countKey = self::REDIS_LIKE_COUNT_PREFIX . $data['id'];

        $likeCount = $this->redis->get($countKey);
        $data['like_count'] = (int) $likeCount ?? 0;

        return  $data;
    }
}